package org.offsync.async

import java.nio.channels.{SelectableChannel => JavaSelectableChannel}
import java.nio.channels.{SelectionKey => JavaSelectionKey}
import java.nio.channels.{Selector => JavaSelector}
import java.nio.channels.CancelledKeyException
import java.nio.channels.ClosedChannelException
import java.nio.channels.SelectionKey
import java.nio.channels.spi.{SelectorProvider => JavaSelectorProvider}
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicReference
import org.offsync.util.DisposableBase
import org.offsync.util.Trace.trace
import org.offsync.rich._
import org.offsync.util.DisposedException
import scala.collection.{mutable => mutable}

abstract class AsyncSelectable(val channel: JavaSelectableChannel) extends DisposableBase {
  def ops: List[AsyncInterest]
  
  def blockingLock: Object = channel.blockingLock
  
  def configureBlocking(block: Boolean): JavaSelectableChannel = channel.configureBlocking(block)
  
  def isBlocking: Boolean = channel.isBlocking
  
  def isRegistered: Boolean = channel.isRegistered
  
  def keyFor(selector: AsyncSelector): JavaSelectionKey = channel.keyFor(selector.selector)
  
  def provider: JavaSelectorProvider = channel.provider
  
  def validOps: Int = channel.validOps
  
  def interestOps(selector: AsyncSelector): Int = ops.foldLeft(0) { (lastOps, interest) =>
    if (interest.isRegisteredWith(selector)) {
      lastOps | interest.op
    } else {
      lastOps
    }
  }
  
  def process(key: SelectionKey)(processCompletion: () => Unit): Unit = {
    val interestOps = try {
      key.interestOps
    } catch {
      case e: CancelledKeyException => {
        this.dispose()
        return
      }
    }
    
    for (slot <- ops) {
      slot.process(key, interestOps)(processCompletion)
    }
  }
  
  def reregister(selector: AsyncSelector): Unit = {
    try {
      val ops = interestOps(selector)
      if (ops != 0) {
        channel.register(selector.selector, ops, this)
      } else {
        val key = channel.keyFor(selector.selector)
        key.cancel()
      }
    } catch {
      case e: CancelledKeyException => {
        e.printStackTrace(System.out)
      }
    }
  }
  
  def register(selector: AsyncSelector, additionalOp: Int)(task: (() => Option[SelectionKey]) => Boolean): Unit = {
    try {
      val interestOps = this.interestOps(selector) | additionalOp
      
      if (interestOps != 0) {
        val key = try {
          channel.register(selector.selector, interestOps, this)
        } catch {
          case e: ClosedChannelException => {
            task { () => throw new DisposedException(e) }
            return
          }
          case e => {
            task { () => throw e.wrap() }
            return
          }
        }
        
        task { () => Some(key) }
      } else {
        val key = channel.keyFor(selector.selector)
        
        if (key != null) {
           key.cancel()
        }
        
        task { () => None }
      }
    } catch {
      case e: DisposedException => {
        task { () => throw new DisposedException(e) }
      }
    }
  }
  
  def closeInterestsFor(key: SelectionKey, interestOps: Int): Unit = {
    for (interest <- ops) {
      interest.process(key, interestOps) { () =>
      }
    }
  }
  
  protected override def onDispose(): Unit = {
    try {
      for (op <- this.ops) {
        op.dispose()
      }
    } catch {
      case e => e.printStackTrace(System.err)
    } finally {
      super.onDispose()
    }
  }
}
